#pragma once

#include <stack>
#include <vector>
#include "input.hpp"
#include "log.hpp"

namespace dfa
{
#define STATE_0 0
#define STATE_1 1

    class PushDownDfa {
    public:
        PushDownDfa()
            : _last_accept(false)
            , _input(new Input())
        { 
            initTransTable();
            inputFromConsole();
            _push_down_stack.push(STATE_0);
        };

        void recogParenthese()
        {
            auto c = (char)_input->advance();
            for ( ; ; ) {
                if (c == '\n') {
                    c = (char)_input->advance();
                    continue;
                }
                std::string receive;
                receive += c;
                if (c == Input::_EOF) receive = "EOF";
                log::logMessage(log::LOG_LEVEL::_DEBUG, "receive char: %s", receive.c_str());
                auto column = getColumnForInPutChar(c);
                auto top_stack_symbol = _push_down_stack.top();
                auto action = _dfa_trans_table[top_stack_symbol][column];
                takeAction(action);
                c = (char)_input->advance();
                if (c == Input::_EOF) break;
            }
        }

    private:
        typedef enum _state_action {
            PUSH_1,
            POP,
            ERROR,
            ACCEPT
        } state_action;

        void initTransTable()
        {
            std::vector<std::vector<state_action>> temp(2, std::vector<state_action>(3, state_action::PUSH_1));
            temp[0][0] = state_action::PUSH_1;
            temp[0][1] = state_action::ERROR;
            temp[0][2] = state_action::ACCEPT;
            temp[1][0] = state_action::PUSH_1;
            temp[1][1] = state_action::POP;
            temp[1][2] = state_action::ERROR;
            _dfa_trans_table.swap(temp);
        }

        int getColumnForInPutChar(char c)
        {
            switch (c) {
                case '(':
                    return 0;
                case ')':
                    return 1;
                case Input::_EOF:
                    return 2;
                default:
                    return -1;
            }
        }

        void takeAction(state_action action)
        {
            switch (action) {
                case PUSH_1:
                    log::logMessage(log::LOG_LEVEL::_DEBUG, "take action by push state 1");
                    _push_down_stack.push(STATE_1);
                    break;
                case POP:
                    log::logMessage(log::LOG_LEVEL::_DEBUG, "take action by pop state 1");
                    _push_down_stack.pop();
                    break;
                case ERROR:
                    log::logMessage(log::LOG_LEVEL::_ERROR, "The input string can not be accepted");
                    exit(1);
                    break;
                case ACCEPT:
                    log::logMessage(log::LOG_LEVEL::_DEBUG, "DFA go into accept state");
                    _last_accept = true;
                    break;
            }
        }

        void inputFromConsole()
        {
            std::cout << "Please input parenthese: " << std::endl;
            _input->newFile();
            _input->advance();
            _input->pushBack(1);
            while (_input->lookAhead(1) == ' ' || _input->lookAhead(1) == '\n') _input->advance();
        }

        std::stack<int> _push_down_stack;
        std::vector<std::vector<state_action>> _dfa_trans_table;
        bool _last_accept;
        Input* _input;
    };
} /* namespace dfa */
